Determine if a string has all unique characters: Difference between revisions
Content added Content deleted
(→{{header|Factor}}: simplify) |
(→{{header|JavaScript}}: Added a variation in terms of sorting and grouping.) |
||
Line 493: | Line 493: | ||
=={{header|JavaScript}}== |
=={{header|JavaScript}}== |
||
<lang javascript>(() => { |
|||
'use strict'; |
|||
// duplicatedCharIndices :: String -> Maybe (Char, [Int]) |
|||
const duplicatedCharIndices = s => { |
|||
const |
|||
duplications = filter(g => 1 < g.length)( |
|||
groupBy(on(eq)(snd))( |
|||
sortBy(comparing(snd))( |
|||
zip(enumFrom(0))(chars(s)) |
|||
) |
|||
) |
|||
); |
|||
return 0 < duplications.length ? (() => { |
|||
const |
|||
firstCase = sortBy(comparing(compose(fst, fst)))( |
|||
duplications |
|||
)[0]; |
|||
return Just(Tuple(firstCase[0][1])( |
|||
firstCase.map(fst) |
|||
)); |
|||
})() : Nothing() |
|||
}; |
|||
// ------------------------TEST------------------------ |
|||
const main = () => |
|||
console.log( |
|||
fTable('First duplicated character, if any:')( |
|||
s => `'${s}'` |
|||
)(maybe('None')(tpl => { |
|||
const [c, ixs] = Array.from(tpl); |
|||
return `'${c}' (0x${showHex(ord(c))}) at ${ixs.join(', ')}` |
|||
}))(duplicatedCharIndices)([ |
|||
"", ".", "abcABC", "XYZ ZYX", |
|||
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" |
|||
]) |
|||
); |
|||
// -----------------GENERIC FUNCTIONS------------------ |
|||
// Just :: a -> Maybe a |
|||
const Just = x => ({ |
|||
type: 'Maybe', |
|||
Nothing: false, |
|||
Just: x |
|||
}); |
|||
// Nothing :: Maybe a |
|||
const Nothing = () => ({ |
|||
type: 'Maybe', |
|||
Nothing: true, |
|||
}); |
|||
// Tuple (,) :: a -> b -> (a, b) |
|||
const Tuple = a => b => ({ |
|||
type: 'Tuple', |
|||
'0': a, |
|||
'1': b, |
|||
length: 2 |
|||
}); |
|||
// chars :: String -> [Char] |
|||
const chars = s => s.split(''); |
|||
// comparing :: (a -> b) -> (a -> a -> Ordering) |
|||
const comparing = f => |
|||
x => y => { |
|||
const |
|||
a = f(x), |
|||
b = f(y); |
|||
return a < b ? -1 : (a > b ? 1 : 0); |
|||
}; |
|||
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c |
|||
const compose = (...fs) => |
|||
x => fs.reduceRight((a, f) => f(a), x); |
|||
// enumFrom :: Enum a => a -> [a] |
|||
function* enumFrom(x) { |
|||
let v = x; |
|||
while (true) { |
|||
yield v; |
|||
v = 1 + v; |
|||
} |
|||
} |
|||
// eq (==) :: Eq a => a -> a -> Bool |
|||
const eq = a => b => a === b; |
|||
// filter :: (a -> Bool) -> [a] -> [a] |
|||
const filter = f => xs => xs.filter(f); |
|||
// fst :: (a, b) -> a |
|||
const fst = tpl => tpl[0]; |
|||
// fTable :: String -> (a -> String) -> (b -> String) |
|||
// -> (a -> b) -> [a] -> String |
|||
const fTable = s => xShow => fxShow => f => xs => { |
|||
// Heading -> x display function -> |
|||
// fx display function -> |
|||
// f -> values -> tabular string |
|||
const |
|||
ys = xs.map(xShow), |
|||
w = Math.max(...ys.map(length)); |
|||
return s + '\n' + zipWith( |
|||
a => b => a.padStart(w, ' ') + ' -> ' + b |
|||
)(ys)( |
|||
xs.map(x => fxShow(f(x))) |
|||
).join('\n'); |
|||
}; |
|||
// groupBy :: (a -> a -> Bool) -> [a] -> [[a]] |
|||
const groupBy = fEq => |
|||
// Typical usage: groupBy(on(eq)(f), xs) |
|||
xs => 0 < xs.length ? (() => { |
|||
const |
|||
tpl = xs.slice(1).reduce( |
|||
(gw, x) => { |
|||
const |
|||
gps = gw[0], |
|||
wkg = gw[1]; |
|||
return fEq(wkg[0])(x) ? ( |
|||
Tuple(gps)(wkg.concat([x])) |
|||
) : Tuple(gps.concat([wkg]))([x]); |
|||
}, |
|||
Tuple([])([xs[0]]) |
|||
); |
|||
return tpl[0].concat([tpl[1]]) |
|||
})() : []; |
|||
// length :: [a] -> Int |
|||
const length = xs => |
|||
// Returns Infinity over objects without finite length. |
|||
// This enables zip and zipWith to choose the shorter |
|||
// argument when one is non-finite, like cycle, repeat etc |
|||
(Array.isArray(xs) || 'string' === typeof xs) ? ( |
|||
xs.length |
|||
) : Infinity; |
|||
// maybe :: b -> (a -> b) -> Maybe a -> b |
|||
const maybe = v => |
|||
// Default value (v) if m is Nothing, or f(m.Just) |
|||
f => m => m.Nothing ? v : f(m.Just); |
|||
// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c |
|||
const on = f => |
|||
g => a => b => f(g(a))(g(b)); |
|||
// ord :: Char -> Int |
|||
const ord = c => c.codePointAt(0); |
|||
// showHex :: Int -> String |
|||
const showHex = n => |
|||
n.toString(16); |
|||
// snd :: (a, b) -> b |
|||
const snd = tpl => tpl[1]; |
|||
// sortBy :: (a -> a -> Ordering) -> [a] -> [a] |
|||
const sortBy = f => xs => |
|||
xs.slice() |
|||
.sort(uncurry(f)); |
|||
// take :: Int -> [a] -> [a] |
|||
// take :: Int -> String -> String |
|||
const take = n => xs => |
|||
'GeneratorFunction' !== xs.constructor.constructor.name ? ( |
|||
xs.slice(0, n) |
|||
) : [].concat.apply([], Array.from({ |
|||
length: n |
|||
}, () => { |
|||
const x = xs.next(); |
|||
return x.done ? [] : [x.value]; |
|||
})); |
|||
// uncurry :: (a -> b -> c) -> ((a, b) -> c) |
|||
const uncurry = f => |
|||
(x, y) => f(x)(y) |
|||
// zip :: [a] -> [b] -> [(a, b)] |
|||
const zip = xs => ys => { |
|||
const |
|||
lng = Math.min(length(xs), length(ys)), |
|||
vs = take(lng)(ys); |
|||
return take(lng)(xs) |
|||
.map((x, i) => Tuple(x)(vs[i])); |
|||
}; |
|||
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] |
|||
const zipWith = f => |
|||
xs => ys => { |
|||
const |
|||
lng = Math.min(length(xs), length(ys)), |
|||
vs = take(lng)(ys); |
|||
return take(lng)(xs) |
|||
.map((x, i) => f(x)(vs[i])); |
|||
}; |
|||
// MAIN --- |
|||
return main(); |
|||
})();</lang> |
|||
{{Out}} |
|||
<pre>First duplicated character, if any: |
|||
'' -> None |
|||
'.' -> None |
|||
'abcABC' -> None |
|||
'XYZ ZYX' -> 'X' (0x58) at 0, 6 |
|||
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' -> '0' (0x30) at 9, 24</pre> |
|||
Or, an alternative to sorting and grouping: |
|||
<lang javascript>(() => { |
<lang javascript>(() => { |
||
'use strict'; |
'use strict'; |